// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: juntang

#pragma once

#include <iomanip>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/time.h"
#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_base/base_auth.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/api_util/json_writer.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

class ApolloxBaseAuth {
 public:
  virtual ~ApolloxBaseAuth() = default;

  static std::string GetSignature(const std::string& secret_key, const std::string& param) {
    if (secret_key.empty()) {
      throw std::runtime_error("[Apollox] API key and secret key are required");
    }
    return ApiSignatureUtil::GetSignatureByHexHmacSHA256(param, secret_key);
  }
};

class ApolloxWsAuth : public ApolloxBaseAuth {
 public:
  explicit ApolloxWsAuth(const std::string& key_filepath) : key_(AuthKey::FromFile(key_filepath)) {}

  const std::string CreateSignature(const std::string& host, const std::string& address) const {
    return "";
  }

 protected:
  AuthKey key_;
};

class ApolloxRestAuth : public ApolloxBaseAuth, public BaseRestAuth {
 public:
  explicit ApolloxRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {
    header_.push_back(std::make_pair("X-MBX-APIKEY", key_.access_key()));
  }
  explicit ApolloxRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)),
        host_(host),
        port_(std::to_string(port)),
        bind_ip_(bind_ip) {
    header_.push_back(std::make_pair("X-MBX-APIKEY", key_.access_key()));
  }

  std::string CreateSignature(UrlParamsBuilder* builder) {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    std::string signa = BuildSignaturePath(key_.secret_key(), query_string);
    if (!query_string.empty()) {
      return "?" + query_string + "&" + signa;
    } else {
      return "?" + signa;
    }
  }

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    std::string signa = BuildSignaturePath(key_.secret_key(), query_string);

    std::string target;
    if (!query_string.empty()) {
      target = address + "?" + query_string + "&" + signa;
    } else {
      target = address + "?" + signa;
    }

    return AsioHttpContext::NewGet(host_, port_, bind_ip_, target, header_);
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    std::string signa = BuildSignaturePath(key_.secret_key(), query_string);

    if (query_string.empty()) {
      if (post_body.empty()) {
        return AsioHttpContext::NewPost(host_, port_, bind_ip_, address, header_, post_body);
      } else {
        return AsioHttpContext::NewPost(
            host_,
            port_,
            bind_ip_,
            address,
            header_,
            post_body + "&" + signa);
      }
    } else if (!post_body.empty()) {
      return AsioHttpContext::NewPost(
          host_,
          port_,
          bind_ip_,
          address + "?" + query_string,
          header_,
          post_body + "&" + signa);
    } else {
      return AsioHttpContext::NewPost(
          host_,
          port_,
          bind_ip_,
          address + "?" + query_string + "&" + signa,
          header_,
          post_body);
    }
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    std::string signa = BuildSignaturePath(key_.secret_key(), query_string);

    if (query_string.empty()) {
      if (post_body.empty()) {
        return AsioHttpContext::NewDelete(host_, port_, bind_ip_, address, header_, post_body);
      } else {
        return AsioHttpContext::NewDelete(
            host_,
            port_,
            bind_ip_,
            address,
            header_,
            post_body + "&" + signa);
      }
    } else if (!post_body.empty()) {
      return AsioHttpContext::NewDelete(
          host_,
          port_,
          bind_ip_,
          address + "?" + query_string,
          header_,
          post_body + "&" + signa);
    } else {
      return AsioHttpContext::NewDelete(
          host_,
          port_,
          bind_ip_,
          address + "?" + query_string + "&" + signa,
          header_,
          post_body);
    }
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();

    return AsioHttpContext::NewPut(host_, port_, bind_ip_, address, header_, post_body);
  }

 public:
  std::string GetHost() { return host_; }
  void SetHost(const std::string& host) { host_ = host; }
  std::vector<std::pair<std::string, std::string>> GetHeader() { return header_; }

 private:
  static std::string BuildSignaturePath(const std::string& secret_key, const std::string& param) {
    std::string code = ApiSignatureUtil::EscapeURL(GetSignature(secret_key, param));
    return "signature=" + code;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
  std::vector<std::pair<std::string, std::string>> header_;
};
